<?php

namespace app\data\service;

use app\data\service\payment\AlipayPaymentService;
use app\data\service\payment\BalancePyamentService;
use app\data\service\payment\EmptyPaymentService;
use app\data\service\payment\JoinPaymentService;
use app\data\service\payment\WechatPaymentService;
use app\devices\service\DevicesService;
use app\devices\service\SocketService;
use app\devices\service\OrderService;

use app\devices\service\VipService;
use think\App;
use think\cache\driver\Redis;
use think\Container;
use think\Db;
use think\Exception;

/**
 * 支付基础服务
 * Class PaymentService
 * @package app\data\service
 */
abstract class PaymentService
{

    // 用户余额支付
    const PAYMENT_EMPTY = 'empty';
    const PAYMENT_BALANCE = 'balance';

    // 汇聚支付参数
    const PAYMENT_JOINPAY_GZH = 'joinpay_gzh';
    const PAYMENT_JOINPAY_XCX = 'joinpay_xcx';

    // 微信商户通道
    const PAYMENT_WECHAT_APP = 'wechat_app';
    const PAYMENT_WECHAT_GZH = 'wechat_gzh';
    const PAYMENT_WECHAT_XCX = 'wechat_xcx';
    const PAYMENT_WECHAT_WAP = 'wechat_wap';
    const PAYMENT_WECHAT_QRC = 'wechat_qrc';

    // 支付宝支付参数
    const PAYMENT_ALIAPY_APP = 'alipay_app';
    const PAYMENT_ALIPAY_WAP = 'alipay_wap';
    const PAYMENT_ALIPAY_WEB = 'alipay_web';

    // 支付参数配置
    const TYPES = [
        // 微信支付配置（不需要的直接注释）
        self::PAYMENT_EMPTY       => [
            'type' => 'EMPTY',
            'name' => '订单无需支付',
            'bind' => [],
        ],
        self::PAYMENT_BALANCE     => [
            'type' => 'BALANCE',
            'name' => '账号余额支付',
            'bind' => [
                UserService::APITYPE_WAP, UserService::APITYPE_WEB,
                UserService::APITYPE_WXAPP, UserService::APITYPE_WECHAT,
                UserService::APITYPE_IOSAPP, UserService::APITYPE_ANDROID,
            ],
        ],
        self::PAYMENT_WECHAT_WAP  => [
            'type' => 'MWEB',
            'name' => '微信WAP支付',
            'bind' => [UserService::APITYPE_WAP],
        ],
        self::PAYMENT_WECHAT_APP  => [
            'type' => 'APP',
            'name' => '微信APP支付',
            'bind' => [UserService::APITYPE_IOSAPP, UserService::APITYPE_ANDROID],
        ],
        self::PAYMENT_WECHAT_XCX  => [
            'type' => 'JSAPI',
            'name' => '微信小程序支付',
            'bind' => [UserService::APITYPE_WXAPP],
        ],
        self::PAYMENT_WECHAT_GZH  => [
            'type' => 'JSAPI',
            'name' => '微信公众号支付',
            'bind' => [UserService::APITYPE_WECHAT],
        ],
        self::PAYMENT_WECHAT_QRC  => [
            'type' => 'NATIVE',
            'name' => '微信二维码支付',
            'bind' => [UserService::APITYPE_WEB],
        ],
        // 支付宝支持配置（不需要的直接注释）
        self::PAYMENT_ALIPAY_WAP  => [
            'type' => '',
            'name' => '支付宝WAP支付',
            'bind' => [UserService::APITYPE_WAP],
        ],
        self::PAYMENT_ALIPAY_WEB  => [
            'type' => '',
            'name' => '支付宝WEB支付',
            'bind' => [UserService::APITYPE_WEB],
        ],
        self::PAYMENT_ALIAPY_APP  => [
            'type' => '',
            'name' => '支付宝APP支付',
            'bind' => [UserService::APITYPE_ANDROID, UserService::APITYPE_IOSAPP],
        ],
        // 汇聚支持配置（不需要的直接注释）
        self::PAYMENT_JOINPAY_XCX => [
            'type' => 'WEIXIN_XCX',
            'name' => '汇聚小程序支付',
            'bind' => [UserService::APITYPE_WXAPP],
        ],
        self::PAYMENT_JOINPAY_GZH => [
            'type' => 'WEIXIN_GZH',
            'name' => '汇聚公众号支付',
            'bind' => [UserService::APITYPE_WECHAT],
        ],
    ];

    /**
     * 当前应用
     * @var App
     */
    protected $app;

    /**
     * 支付参数编号
     * @var string
     */
    protected $code;

    /**
     * 默认支付类型
     * @var string
     */
    protected $type;

    /**
     * 当前支付参数
     * @var array
     */
    protected $params;

    /**
     * 支付服务对象
     * @var array
     */
    protected static $driver = [];

    /**
     * PaymentService constructor.
     * @param App $app 当前应用对象
     * @param string $code 支付参数编号
     * @param string $type 支付类型代码
     * @param array $params 支付参数配置
     */
    public function __construct(App $app, string $code, string $type, array $params)
    {
        [$this->app, $this->code, $this->type, $this->params] = [$app, $code, $type, $params];
        if (method_exists($this, 'initialize')) $this->initialize();
    }

    /**
     * 根据配置实例支付服务
     * @param string $code 支付参数编号
     * @return JoinPaymentService|WechatPaymentService|AlipayPaymentService
     * @throws Exception
     */
    public static function instance(string $code): PaymentService
    {
        if ($code === 'empty') {
            $vars = ['code' => 'empty', 'type' => 'empty', 'params' => []];
            return static::$driver[$code] = Container::getInstance()->make(EmptyPaymentService::class, $vars);
        }
        [, $type, $params] = self::config($code);
        if (isset(static::$driver[$code])) return static::$driver[$code];
        $vars = ['code' => $code, 'type' => $type, 'params' => $params];
        // 实例化具体支付参数类型
        if (stripos($type, 'balance') === 0) {
            return static::$driver[$code] = Container::getInstance()->make(BalancePyamentService::class, $vars);
        } elseif (stripos($type, 'alipay_') === 0) {
            return static::$driver[$code] = Container::getInstance()->make(AlipayPaymentService::class, $vars);
        } elseif (stripos($type, 'wechat_') === 0) {
            return static::$driver[$code] = Container::getInstance()->make(WechatPaymentService::class, $vars);
        } elseif (stripos($type, 'joinpay_') === 0) {
            return static::$driver[$code] = Container::getInstance()->make(JoinPaymentService::class, $vars);
        } else {
            throw new Exception(sprintf('支付驱动[%s]未定义', $type));
        }
    }

    /**
     * 获取支付通道名称
     * @param string $type
     * @return string
     */
    public static function name(string $type): string
    {
        return self::TYPES[$type]['name'] ?? $type;
    }

    /**
     * 获取支付类型
     * @return array
     */
    public static function types(): array
    {
        $types = [];
        foreach (self::TYPES as $k => $v) if (isset($v['bind'])) {
            if (array_intersect($v['bind'], array_keys(UserService::TYPES))) {
                $types[$k] = $v;
            }
        }
        return $types;
    }

    /**
     * 获取通道配置参数
     * @param string $code
     * @param array $payment
     * @return array [code,type,params]
     * @throws Exception
     */
    public static function config(string $code, array $payment = []): array
    {
        try {
            if (empty($payment)) {
                $map = ['code' => $code, 'status' => 1, 'deleted' => 0];
                $payment = app()->db->name('ShopPayment')->where($map)->find();
            }
            if (empty($payment)) {
                throw new Exception("支付参数[#{$code}]禁用关闭");
            }
            $params = @json_decode($payment['content'], true);
            if (empty($params)) {
                throw new Exception("支付参数[#{$code}]配置无效");
            }
            if (empty(static::TYPES[$payment['type']])) {
                throw new Exception("支付参数[@{$payment['type']}]匹配失败");
            }
            return [$payment['code'], $payment['type'], $params];
        } catch (\Exception $exception) {
            throw new Exception($exception->getMessage(), $exception->getCode());
        }
    }

    /**
     * 订单更新操作
     * @param string $orderNo 订单单号
     * @param string $paymentTrade 交易单号
     * @param string $paymentAmount 支付金额
     * @param string $paymentRemark 支付描述
     * @return boolean
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function updateOrder(string $orderNo, string $paymentTrade, string $paymentAmount, $paymentRemark = '在线支付'): bool
    {
        // 检查订单支付状态
        $map = ['order_no' => $orderNo, 'payment_status' => 0, 'status' => 2];
        $order = $this->app->db->name('ShopOrder')->where($map)->find();
        if (empty($order)) return false;
        // 更新订单支付状态
        $data = [
            'status'           => 3,
            'payment_type'     => $this->type,
            'payment_code'     => $this->code,
            'payment_trade'    => $paymentTrade,
            'payment_amount'   => $paymentAmount,
            'payment_remark'   => $paymentRemark,
            'payment_status'   => 1,
            'payment_datetime' => date('Y-m-d H:i:s'),
        ];
        if (empty($data['payment_type'])) unset($data['payment_type']);
        $this->app->db->name('ShopOrder')->where($map)->update($data);
        // 触发订单更新事件
        $this->app->event->trigger('ShopOrderPayment', $orderNo);
        return true;
    }

    /**
     * 创建支付行为
     * @param string $orderNo 商户订单单号
     * @param string $paymentTitle 商户订单标题
     * @param string $paymentAmount 需要支付金额
     */
    protected function createPaymentAction(string $orderNo, string $paymentTitle, string $paymentAmount)
    {
        $this->app->db->name('ShopPaymentItem')->insert([
            'payment_code' => $this->code, 'payment_type' => $this->type,
            'order_amount' => $paymentAmount, 'order_name' => $paymentTitle, 'order_no' => $orderNo,
        ]);
    }

    /**
     * 更新支付记录并更新订单
     * @param string $orderNo 商户订单单号
     * @param string $paymentTrade 平台交易单号
     * @param string $paymentAmount 实际到账金额
     * @param string $paymentRemark 平台支付备注
     * @param string $scene 场景
     * @return boolean
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    protected function updatePaymentAction(string $orderNo, string $paymentTrade, string $paymentAmount, string $paymentRemark = '在线支付', $scene="order"): bool
    {
        if($scene=='order'){
            // 更新支付记录
            data_save('ShopPaymentItem', [
                'order_no'         => $orderNo,
                'payment_code'     => $this->code,
                'payment_type'     => $this->type,
                'payment_trade'    => $paymentTrade,
                'payment_amount'   => $paymentAmount,
                'payment_status'   => 1,
                'payment_datatime' => date('Y-m-d H:i:s'),
            ], 'order_no', [
                'payment_code' => $this->code,
                'payment_type' => $this->type,
            ]);
            // 更新记录状态
            return $this->updateOrder($orderNo, $paymentTrade, $paymentAmount, $paymentRemark);
        }else if($scene=='recharge'){
            $order = $this->app->db->name('data_user_balance')->where('code', $orderNo)->find();
            $data = [
                'deleted'=>0,
                'trade_no'=>$paymentTrade,
                'status'=>1,
                'remark'=>'用户充值'
            ];
            $res = $this->app->db->name('data_user_balance')->where('id', $order['id'])->update($data);
            if($res){
                UserService::instance()->balance($order['uid']);
                return true;
            }else{
                return false;
            }
        }else if($scene=='vip_order'){
            $order = $this->app->db->name('jcg_vip_order')->where('order_no', $orderNo)->find();
            $this->app->db->startTrans();
            // 更新会员购买记录
            $data = [
                'id'            => $order['id'],
                'status'        => 1,
                'trade_no'      => $paymentTrade
            ];

            $res = $this->app->db->name('jcg_vip_order')->where('id', $order['id'])->update($data);
            if($res){
                $extends = json_decode($order['extends'], true);

                //更新会员有效期
                VipService::instance()->setUserVip($order['user_id'], $extends['days']);
                $this->app->db->commit();
                return true;
            }else{
                return false;
            }
        }else{
            $order = OrderService::instance()->get($orderNo);
            if(!empty($order)){
                $this->app->db->startTrans();
                // 更新支付记录
                $data = [
                    'id'            => $order['id'],
                    'order_no'      => $orderNo,
                    'status'        => 1,
                    'is_check'      => 1,
                    'trade_no'      => $paymentTrade,
                    'create_time'   => time(),
                ];

                $res = $this->app->db->name('jcg_devices_order')->where('id', $order['id'])->update($data);
                $res1 = 1;
                $redis = new Redis();
                if($scene=='out_order'){
                    if($res){
                        $res = $this->app->db->name('jcg_devices_order')->where('id', $order['pid'])->inc("money", $order['money'])->update();
                        if($res){
                            $redis->set("OutOrder".$order['id'], $order['money'],300);
                        }else{
                            return false;
                        }
                        $this->app->db->commit();
                        return true;
                    }else{
                        return false;
                    }
                }

                $pick_data = $redis->get('PickUpCode:'.$order['user_id']);
                if($pick_data){
                    $res1 = DevicesService::instance()->setPickUpCode($pick_data);
                }
                if($res && $res1){
                    $device = DevicesService::instance()->getDevice($order['d_id']);
                    $msg = [
                        'type'=>2,
                        'device_id'=>$device['device_id'],
                        'ch'=>$order['board_channel'],
                        'device_board'=>$order['device_board']
                    ];
                    SocketService::instance()->sendSocket($msg);
                    //添加redis 开锁命令
                    $redis->handler()->ZADD("UnLockCommand", $order['user_id'], $order['d_id'].'_'.$order['device_board'].'_'.$order['board_channel']);

                    jcgoplog('开锁','用户付款开锁', UserService::instance()->getNickName($order['user_id']), $order['user_id'], $order['id'], $order['box_id']);

                    $redis->set("PayBox_".$order['d_id'].'_'.$order['device_board'].'_'.$order['board_channel'], $order['id'],86400*3);

                    //添加redis 订单已使用箱子
                    $redis->handler()->ZADD("orderUseBox".$order['d_id'], $order['user_id'], $order['box_id']);
                    try{
                        //用户是否关注公众号
                        $openid = UserService::instance()->getOpenid($order['user_id']);
                        if($openid){
                            //发送模板通知
                            $extends = json_decode($order['extends'], true);
                            OrderService::instance()->sendTemp(1, $openid, [$extends['device_name'], ($extends['box_num']<10?'0'.$extends['box_num']:$extends['box_num']).'号', $order['money'].'元', date("Y-m-d H:i")],'','请在营业时间内取包，如有疑问，请联系：'.($extends['store_contact']??''));

                            //添加redis 未关门提醒
                            $noLockMsg = [time(), 4, $openid, [$extends['device_name'], ($extends['box_num']<10?'0'.$extends['box_num']:$extends['box_num']).'号', date('Y-m-d H:i:s')], '请及时关好柜门，感谢您的配合。如有问题，请联系：'.($extends['store_contact']??'')];
                            $redis->set($order['d_id'].'_'.$order['device_board'].'_'.$order['board_channel'], $noLockMsg, 22*60);
                        }
                    }catch (\Exception $e){

                    }

                    DevicesService::instance()->setDeviceUser($order['user_id'], $order['d_id']);

                    $this->app->db->commit();
                    return true;
                }else{
                    $this->app->db->rollback();
                    return false;
                }
            }else{
                return false;
            }
        }

    }

    /**
     * 订单主动查询
     * @param string $orderNo
     * @return array
     */
    abstract public function query(string $orderNo): array;

    /**
     * 支付通知处理
     * @return string
     */
    abstract public function notify(): string;

    /**
     * 创建支付订单
     * @param string $openid 会员OPENID
     * @param string $orderNo 交易订单单号
     * @param string $paymentAmount 交易订单金额（元）
     * @param string $paymentTitle 交易订单名称
     * @param string $paymentRemark 交易订单描述
     * @param string $paymentReturn 支付回跳地址
     * @return array
     */
    abstract public function create(string $openid, string $orderNo, string $paymentAmount, string $paymentTitle, string $paymentRemark, string $paymentReturn = ''): array;
}